/*
 * parser.c
 *
 * Group: Fantastic Five
 * Date: 4/29/08
 *
 * Description: parser functions, checks that program contains valid instructions and operands
 */

#include "instr_list.h"
#include "parser.h"

/* Main parsing function */
int parse(FILE * out, FILE * err, struct token_list *tl, struct instr_list *il)
{
    int num_errors = 0;
    int i;
    struct token *P_Main;
    struct token *P_Peak;

    if (tl->count % 3 != 0) // checks to see if size of token list is invalid
    {
        fprintf(err, "Program length is invalid -> You have an invalid number of operands!\n");
        ++num_errors;
        return num_errors;
    }

    P_Main = tl->head;  // Sets Main Pointer to point to first token in token_list
    P_Peak = P_Main;    // Sets Peak Pointer to point to first token in token_list

    // BEGIN BRIEF PASS
    if(P_Main->type != OPCODE || P_Main->v.op != BEG)   // makes sure first token is the OPCODE  BEG
    {
        ++num_errors;
        fprintf(err, "No BEG statement!\n");
    }
    
    P_Main = P_Main->next->next->next;  // moves to 4th token (skips 2 dummy operands)

    for(i = 0; i < ((tl->count) / 3) - 2; ++i)  // loops though token list(from 4th to last_token-3) & checks that every 3rd is an OPCODE
    {                                           // from 4th to last_token-3 because it is only checking between BEG & END
        if(P_Main->type != OPCODE)
        {
            ++num_errors;
            fprintf(err, "Invalid number of operands on line %i.\n", P_Main->linenum);
        }

        P_Main = P_Main->next->next->next;  // jumps 2 operands to next opcode
    }
    
    if(P_Main->type != OPCODE || P_Main->v.op != END)   // makes sure last token is the OPCODE  END
    {
        ++num_errors;
        fprintf(err, "No END statement!\n");
    }
    // END BRIEF PASS

    // BEGIN THOROUGH PASS
    if(num_errors == 0)
    {
        P_Main = P_Peak;    // Sets P_Main back to the head
        
        for(i = 0; i < (tl->count) / 3; ++i)    // Loop 2 -- Looks @ Each OPCODE & validates it's operands
        {
            P_Peak = P_Main->next;

            if(P_Main->v.op == ADD  || P_Main->v.op == MUL)
            {
                // Reg,Reg || Reg,Struct
                if (!(P_Peak->type == REGISTER && (P_Peak->next->type == REGISTER 
                                || P_Peak->next->type == INTEGER 
                                || P_Peak->next->type == FLOAT)))
                {
                    ++num_errors;
                    fprintf(err, "Invalid operand(s) on line %i.\n", P_Main->linenum);
                }
            }
            else if(P_Main->v.op == SUB  || P_Main->v.op == DIV)
            {
                // Reg,Reg || Reg,Struct || Struct,Reg
                if (!((P_Peak->type == REGISTER && (P_Peak->next->type == REGISTER 
                                || P_Peak->next->type == INTEGER 
                                || P_Peak->next->type == FLOAT))
                                || ((P_Peak->type == INTEGER || P_Peak->type == FLOAT) 
                                && P_Peak->next->type == REGISTER)))
                {
                    ++num_errors;
                    fprintf(err, "Invalid operand(s) on line %i.\n", P_Main->linenum);
                }
            }
            else if(P_Main->v.op == JZ  || P_Main->v.op == JNZ || P_Main->v.op == JGT || P_Main->v.op == JLT)
            {
                if (!(P_Peak->type == REGISTER && P_Peak->next->type == INTEGER))   // Reg,LINE#
                {
                    ++num_errors;
                    fprintf(err, "Invalid operand(s) on line %i.\n", P_Main->linenum);
                }
            }
            else if(P_Main->v.op == MOV)
            {
                if (!(P_Peak->type == REGISTER && (P_Peak->next->type == INTEGER || P_Peak->next->type == FLOAT)))  // Reg,STRUCT
                {
                    ++num_errors;
                    fprintf(err, "Invalid operand(s) on line %i.\n", P_Main->linenum);
                }
            }
            else if(P_Main->v.op == CPY || P_Main->v.op == CMP)
            {
                if (!(P_Peak->type == REGISTER && P_Peak->next->type == REGISTER))  // Reg,Reg
                {
                    ++num_errors;
                    fprintf(err, "Invalid operand(s) on line %i.\n", P_Main->linenum);
                }
            }
            // START OF SINGLE OPERAND INSTRUCTIONS --- LEXER PASSES (OPERAND, NONE)
            else if(P_Main->v.op == INC || P_Main->v.op == DEC || P_Main->v.op == GET || P_Main->v.op == PUT || P_Main->v.op == CLR)
            {
                if (!(P_Peak->type == NONE && P_Peak->next->type == REGISTER))  // Reg
                {
                    ++num_errors;
                    fprintf(err, "Invalid operand(s) on line %i.\n", P_Main->linenum);
                }
            }
            else if(P_Main->v.op == JMP)
            {
                if (!(P_Peak->type == NONE && P_Peak->next->type == INTEGER))   // Line#
                {
                    ++num_errors;
                    fprintf(err, "Invalid operand(s) on line %i.\n", P_Main->linenum);
                }
            }
            else if(P_Main->v.op == PTC)
            {
                if (!(P_Peak->type == NONE && P_Peak->next->type == CHAR))  // CHAR
                {
                    ++num_errors;
                    fprintf(err, "Invalid operand(s) on line %i.\n", P_Main->linenum);
                }
            }

            P_Peak = P_Peak->next->next;    // jumps P_Peak to next token after 2 oparands
            P_Main = P_Peak;    // Jumps main pointer up to peaking pointer
        }
    }
    else 
    {
        return num_errors;
    }

    if (num_errors == 0)    // checks to see if still 0 errors after 2nd loop
    {
        /* 2nd Pass through token list (assuming no errors) */
        struct token *currToken = tl->head;
        /* loop through token list */
        while (currToken != NULL)
        {
            struct instr *i;
            /* opcodes with one operand have empty tokens for first operand, must switch order */
            /* OPCODE->EMPTY_TOKEN->1ST_OPERAND */
            if (currToken->v.op == JMP ||
                    currToken->v.op == INC ||
                    currToken->v.op == DEC ||
                    currToken->v.op == GET ||
                    currToken->v.op == PUT ||
                    currToken->v.op == PTC ||
                    currToken->v.op == CLR)
            {
                i = new_instr (currToken->linenum,
                        currToken->v.op,
                        currToken->next->next->v,
                        currToken->next->next->type,
                        currToken->next->v,
                        currToken->next->type);
            }
            /* otherwise, opcode either has 2 operands or none, order is irrelevant */
            else
            {
                i = new_instr (currToken->linenum,
                        currToken->v.op,
                        currToken->next->v,
                        currToken->next->type,
                        currToken->next->next->v,
                        currToken->next->next->type);
            }
            /* insert new instruction */
            push_instr(i, il);
            /* jump to next opcode */
            currToken = currToken->next->next->next;
        }

        return num_errors;
    }
    else
    {
        return num_errors;
    }
}
